#include <uart.h>
#include <util.h>
#include <ioctl.h>
#include <errno.h>

static int uart_open(struct tty *tty)
{
	struct uart *uart = tty_to_uart(tty);
	struct uart_driver *udrv = tty_to_uart_driver(tty);
	int ret = 0;
	
	if (udrv->ops->startup)
		ret = udrv->ops->startup(uart);
	
	if (!ret) {
		if (udrv->ops->start_tx)
			udrv->ops->start_tx(uart);
		if (udrv->ops->start_rx)
			udrv->ops->start_rx(uart);
	}
	
	return ret;
}

static void uart_close(struct tty *tty)
{
	struct uart *uart = tty_to_uart(tty);
	struct uart_driver *udrv = tty_to_uart_driver(tty);

	if (udrv->ops->stop_tx)
		udrv->ops->stop_tx(uart);
	if (udrv->ops->stop_rx)
		udrv->ops->stop_rx(uart);
	if (udrv->ops->shutdown)
		udrv->ops->shutdown(uart);
}

static int uart_read(struct tty *tty, unsigned char *buf, unsigned int count)
{
	struct uart *uart = tty_to_uart(tty);
	struct uart_driver *udrv = tty_to_uart_driver(tty);
	struct device *dev = tty_to_device(tty);
	unsigned int n = 0;
	unsigned char ch, fg;
	int ret;

	while (n < count) {
		if (dev_oflags_is_nonblock(dev->oflags) && udrv->ops->is_rx_empty(uart)) {
			return n;
		}
		ret = udrv->ops->recv_char(uart, &ch, &fg);
		if (!ret) {
			*buf++ = ch;
			n++;
		}
	}

	return n;
}

static int uart_write(struct tty *tty, const unsigned char *buf, unsigned int count)
{
	struct uart *uart = tty_to_uart(tty);
	struct uart_driver *udrv = tty_to_uart_driver(tty);
	struct device *dev = tty_to_device(tty);
	unsigned int n = 0;
	int ret;

	while (n < count) {
		if (dev_oflags_is_nonblock(dev->oflags) && udrv->ops->is_tx_full(uart)) {
			return n;
		}
		ret = udrv->ops->send_char(uart, *buf, 0);
		if (!ret) {
			buf++;
			n++;
		}		
	}

	return n;
}

static int uart_ioctl(struct tty *tty, unsigned int cmd, unsigned int arg)
{
	struct uart *uart = tty_to_uart(tty);
	struct uart_driver *udrv = tty_to_uart_driver(tty);
	void *p = (void *)arg;
	int ret;

	switch(cmd) {
		case UIOGDBITS:
			ret = udrv->ops->get_data_bits(uart);
			if (!IS_ERR(ret)) {
				*((uint8_t *)p) = (uint8_t)ret;
				ret = 0;
			}
			return ret;

		case UIOSDBITS:
			ret = udrv->ops->set_data_bits(uart, *((uint8_t *)p));
			return ret;

		case UIOGSBITS:
			ret = udrv->ops->get_stop_bits(uart);
			if (!IS_ERR(ret)) {
				*((uint8_t *)p) = (uint8_t)ret;
				ret = 0;
			}
			return ret;
			
		case UIOSSBITS:
			ret = udrv->ops->set_stop_bits(uart, *((uint8_t *)p));
			return ret;
		
		case UIOGBAUD:
			ret = udrv->ops->get_baudrate(uart);
			if (!IS_ERR(ret)) {
				*((uint32_t *)p) = (uint32_t)ret;
				ret = 0;
			}
			return ret;
		
		case UIOSBAUD:
			ret = udrv->ops->set_baudrate(uart, *((uint32_t *)p));
			return ret;

		case UIOGPBIT:
			ret = udrv->ops->get_parity_bit(uart);
			if (!IS_ERR(ret)) {
				*((uint8_t *)p) = (uint8_t)ret;
				ret = 0;
			}
			return ret;
		
		case UIOSPBIT:			
			ret = udrv->ops->set_parity_bit(uart, *((uint32_t *)p));
			return ret;

		case UIOSBREAK:
			ret = udrv->ops->send_char(uart, 0, UART_CHAR_BREAK);
			return ret;
		
		case UIOGCHCF:
			{
				struct char_with_flags *cp = (struct char_with_flags *)p;
				unsigned char ch, fg;
				
				ret = udrv->ops->recv_char(uart, &ch, &fg);
				cp->ch = ch;
				cp->fg = fg;
				return ret;
			}
		
		default:
			if (udrv->ops->ioctl) {
				ret = udrv->ops->ioctl(uart, cmd, arg);
				return ret;
			}
	}

	return -ENOTSUPP;
}

static void uart_flush(struct tty *tty)
{
	struct uart *uart = tty_to_uart(tty);
	struct uart_driver *udrv = tty_to_uart_driver(tty);

	while (!udrv->ops->is_tx_empty(uart));
}

static int uart_match(struct device *dev, struct tty_driver *tdrv)
{
	struct uart_driver *udrv = tty_driver_to_uart_driver(tdrv);

	if (udrv->ops->match)
		return udrv->ops->match(dev, udrv);
	
	return -ENOTSUPP;
}

static int uart_probe(struct tty *tty, struct tty_driver *tdrv)
{
	struct uart *uart = tty_to_uart(tty);
	struct uart_driver *udrv = tty_driver_to_uart_driver(tdrv);

    if (udrv->ops->probe)
        return udrv->ops->probe(uart, udrv);

    return 0;
}

struct tty_operations uart_ops = {
	.open	= uart_open,
	.close	= uart_close,
	.read	= uart_read,
	.write	= uart_write,
	.ioctl	= uart_ioctl,
	.flush	= uart_flush,
	.match	= uart_match,
	.probe	= uart_probe,
};

